// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 Ivan Baidakou

#include "common.h"
#include <rotor-light.hpp>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/wdt.h>
#include <string.h>

#define LED PB5

void send_usart(const char *data);
void send_usart(const char *data, size_t count);

namespace rl = rotor_light;

namespace message {
struct Ping : rl::Message {
  using Message::Message;
  static constexpr auto type_id = __LINE__;
  rl::MessageTypeId get_type_id() const override { return type_id; }
};

struct Pong : rl::Message {
  using Message::Message;
  static constexpr auto type_id = __LINE__;
  rl::MessageTypeId get_type_id() const override { return type_id; }
};
} // namespace message

struct Pinger : rl::Actor<2> {
  using Parent = Actor<2>;

  void initialize() override {
    subscribe(&Pinger::on_pong);
    Parent::initialize();
  }

  void advance_start() override {
    Parent::advance_start();
    ping();
  }

  void ping() {
    /* toggle led */
    PORTB ^= (1 << LED);
    send_usart("ping\r\n");
    send<message::Ping>(0, ponger_id);
  }

  void on_pong(message::Pong &) {
    add_event(
        500000, [](void *data) { static_cast<Pinger *>(data)->ping(); }, this);
  }

  rl::ActorId ponger_id;
};

struct Ponger : rl::Actor<2> {
  using Parent = Actor<2>;

  void initialize() override {
    subscribe(&Ponger::on_ping);
    Parent::initialize();
  }
  void on_ping(message::Ping &) {
    //
    send<message::Pong>(0, pinger_id);
  }
  rl::ActorId pinger_id;
};

using Supervisor =
    rl::Supervisor<rl::SupervisorBase::min_handlers_amount, Pinger, Ponger>;
using Storage = rl::traits::MessageStorage<rl::message::ChangeState,
                                           rl::message::ChangeStateAck,
                                           message::Ping, message::Pong>;
using Queue = rl::Queue<Storage, 5>; /* upto 5 messages in 1 queue */
using Planner = rl::Planner<1>;      /* upto 1 time event */

static void app_hw_init();

/* allocate */
Queue queue;
Planner planner;
rl::Context context{&queue, &planner, &get_now};
Supervisor sup;

int main(int, char **) {

  app_hw_init();

  /* setup */
  sup.bind(context);
  auto pinger = sup.get_child<0>();
  auto ponger = sup.get_child<1>();
  pinger->ponger_id = ponger->get_id();
  ponger->pinger_id = pinger->get_id();
  /* let it polls timer */
  sup.start(true);

  send_usart("start\r\n");
  /* main cycle */
  sup.process();
  return 0;
}

static void app_hw_init() {
  // Initialize the application including WDT, PORTB.5 and TIMER0

  // We will now disable the watchdog.
  // Service the watchdog just to be sure to avoid pending timeout.
  wdt_reset();

  // Clear WDRF in MCUSR.
  MCUSR &= ~(1U << WDRF);

  // Write logical one to WDCE and WDE.
  // Keep the old prescaler setting to prevent unintentional time-out.
  WDTCSR |= (1U << WDCE) | (1U << WDE);

  // Turn off the WDT.
  WDTCSR = 0x00;

  // We will now initialize PORTB.5 to be used as an LED driver port.
  // Set PORTB.5 value to low.
  PORTB &= ~(1U << PORTB5);

  /* initializing PB5 which is connected to port 13 of uno as output*/
  DDRB |= (1 << LED);

  enable_timer();

  // USART
#define BAUD 9600
#include <util/setbaud.h>
  UBRR0H = UBRRH_VALUE;
  UBRR0L = UBRRL_VALUE;
  UCSR0B = (1 << RXEN0) | (1 << TXEN0);

  /* Set frame format: 8data, 2stop bit */
  UCSR0C = (1 << USBS0) | (3 << UCSZ00);
  // Enable all interrupts.
  sei();
}

void send_usart(const char *data) { send_usart(data, strlen(data)); }

void send_usart(const char *data, size_t count) {
  auto end = data + count;
  while (data != end) {
    while (!(UCSR0A & (1 << UDRE0)))
      ;
    UDR0 = static_cast<uint8_t>(*data++);
  }
}
